package cz.vutbr.fit.xzemko01.tfmcbs.validation.validators;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.DiagnosticChain;

import cz.vutbr.fit.xzemko01.tfmcbs.model.Component;
import cz.vutbr.fit.xzemko01.tfmcbs.model.Element;
import cz.vutbr.fit.xzemko01.tfmcbs.model.Entity;
import cz.vutbr.fit.xzemko01.tfmcbs.model.Generalization;
import cz.vutbr.fit.xzemko01.tfmcbs.validation.utils.DiagnosticBuilder;


public final class EntityValidator implements Validator<Entity> {
	
	private static EntityValidator entityValidator;
	
	private Validator<Element> elementValidator;
	
	private EntityValidator() {
		elementValidator = ElementValidator.getInstance();
	}
	
	public static Validator<Entity> getInstance() {
		if(entityValidator == null) {
			entityValidator = new EntityValidator();
		}
		return entityValidator;
	}
	
	@Override
	public boolean validate(Entity entity, DiagnosticChain diagnostics, Map<Object, Object> context) {
		boolean result = elementValidator.validate(entity, diagnostics, context);
		result &= validateEntityHasName(entity, diagnostics, context);
		result &= validateEntityHasNonCyclicGeneralization(entity, diagnostics, context);
		result &= validateEntityHasUniqueName(entity, diagnostics, context);
		result &= validateHasUniqueExtends(entity, diagnostics, context);
		return result;
	}
	
	private boolean validateEntityHasName(Entity entity, DiagnosticChain diagnostics, Map<Object, Object> context) {
		String name = entity.getName();
		boolean result = true;
		
		if(name == null || name.trim().isEmpty()) {
			String entityTypeName = "Interface's";
			if(entity instanceof Component) {
				entityTypeName = "Component's";
			} 
			
			Diagnostic diagnostic = new DiagnosticBuilder(entity)
				.setMessage(entityTypeName + " name is empty.").build();
			diagnostics.add(diagnostic);
			result = false;
		}
		return result;
	}
	
	private boolean validateEntityHasNonCyclicGeneralization(Entity entity, DiagnosticChain diagnostics, Map<Object, Object> context) {
		boolean hasCyclicGeneralization = isDuplicationInEntityExtending(new ArrayList<Entity>(), entity);
		if(hasCyclicGeneralization) {
			Diagnostic diagnostic = new DiagnosticBuilder(entity)
					.setMessage("Entity " + entity.getName() + " is in cyclic generalization.").build();
			diagnostics.add(diagnostic);
		}
		
		return !hasCyclicGeneralization;
	}
	
	private boolean isDuplicationInEntityExtending(List<Entity> foundEntities, Entity rootNode) {
		if(rootNode == null) {
			return false;
		}
		if(foundEntities.contains(rootNode)) {
			return true;
		}
		
		List<Generalization> childGeneralizations = rootNode.getChilds();
		if(childGeneralizations == null) {
			return false;
		}
		
		foundEntities.add(rootNode);
		
		boolean result = false;
		for(Generalization generalization : childGeneralizations) {
			Entity child = generalization.getChild();
			result = isDuplicationInEntityExtending(foundEntities, child);
			if(result) {
				break;
			}
		}
		foundEntities.remove(rootNode);
		return result;
	}
	
	private boolean validateEntityHasUniqueName(Entity entity, DiagnosticChain diagnostics, Map<Object, Object> context) {
		
		if(entity == null || entity.getName() == null || entity.getName().trim().isEmpty()) {
			return true;
		}
		
		List<Element> elementList = entity.getContainer().getElements();
		if(elementList == null) {
			return true;
		}
		
		for(Element element : elementList) {
			if(!(element instanceof Entity) || element == entity) {
				continue;
			}
			
			if(entity.getName().equals(element.getName())) {
				Diagnostic diagnostic = new DiagnosticBuilder(entity)
					.setMessage("Entity \"" + entity.getName() + "\" must have unique name.").build();
				diagnostics.add(diagnostic);
				return false;
			}			
		}
		return true;
	}
	
	private boolean validateHasUniqueExtends(Entity entity, DiagnosticChain diagnostics, Map<Object, Object> context) {
		if(entity == null || entity.getExtends() == null) {
			return true;
		}
		
		int i = 0;
		boolean result = true;
		
		for(Generalization extend1 : entity.getExtends()) {
			i++;
			for(int j = i; j < entity.getExtends().size(); j++) {
				Generalization extend2 = entity.getExtends().get(j);
				
				if(extend1.getChild() != null
					&& extend2.getChild() != null
					&& extend1.getChild() == extend2.getChild()) 
				{
					Diagnostic diagnostic = new DiagnosticBuilder(entity)
						.setMessage(
								"Entity \"" + entity.getName() + 
								"\" extends entity \"" + extend1.getParent().getName() + "\" two or more times.")
						.build();
					diagnostics.add(diagnostic);
					result = false;
				}
			}
		}
		
		return result;
	}
}
